{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 问题 1：__new__和__init__的区别\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": true
   },
   "source": [
    "对于很多人来说，__new__和__init__这两个方法都搞不太清楚。可能你会觉得__new__是不是没有存在的必要，因为实在很少有机会去用它。\n",
    "\n",
    "答案：\n",
    "\n",
    "__new__:\n",
    "\n",
    "是静态方法\n",
    "用于创建实例\n",
    "第一个参数是类型并且（通常）返回该类型的新实例\n",
    "__init__:\n",
    "\n",
    "是实例方法\n",
    "用于初始化实例\n",
    "第一个参数是实例本身，不返回任何东西\n",
    "问题中涉及的知识点：\n",
    "\n",
    "构造器与实例初始化\n",
    "知识点解析：\n",
    "\n",
    "我们很少有机会直接调用__new__()，因为它是不会被显式地调用。我们可以这么理解__new__() 和__init__这两个方法，Python 先通过构造器__new__() 去分配内存，并创建了实例，再通过__init__去初始化实例。\n",
    "\n",
    "Tips: __new__() 不一定要返回实例，它可以返回任何东西。但是如果它不返回实例的，接下来 Python 也不会自动初始化实例。\n",
    "\n",
    "那什么时候用__new__呢？\n",
    "\n",
    "__new__() 的实际目的主要是允许不可变类型的子类 (例如 int, str 或 tuple) 定制实例创建过程。它也常会在自定义元类中被重载以便定制类创建过程。\n",
    "\n",
    "Tips：所以，请记住：如果你想要继承某个 Python 内建的不可变类型，例如 int, str 或 tuple，你就必须使用__new__()。因为对于这些不可变类型，如果在初始化的过程去改变某些值，可能已经太晚了。接下来用下面两个例子帮助你们理解。\n",
    "\n",
    "class PositiveInt(int):  # 错误\n",
    "...     def __init__(self, arg):\n",
    "...         if arg < 0:\n",
    "...             self = -arg\n",
    "...\n",
    ">>> n = PositiveInt(-1)\n",
    ">>> n\n",
    "-1\n",
    "上面这个例子尝试在初始化的时候去改变实例，但是并没有按照预想去实现初始化成为一个负数。这是因为实例是继承 int，而 int 对象是不可变的，一旦创建就无法改变。\n",
    "\n",
    " class PositiveInt(int):  # 正确\n",
    "...     def __new__(cls, arg):\n",
    "...         if arg < 0:\n",
    "...             arg = -arg\n",
    "...         return super().__new__(cls, arg)\n",
    "...\n",
    ">>>> n = PositiveInt(-1)\n",
    ">>>> n\n",
    "1\n",
    "而这次就不同了，在创建实例之前，即在 return super().__new__(cls, arg) 这一步之前去改变。\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 问题 2：简述 with 方法打开处理文件帮我们做了什么？\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "任何编程语言，文件操作或数据库连接等资源的使用是非常常见的。但是这些资源的供应有限。因此，主要问题在于确保在使用后释放这些资源。如果未释放它们，则会导致资源泄漏，并可能导致系统变慢或崩溃。如果用户有一个自动设置和拆解资源的机制，这将是非常有用的。在 Python 中，可以通过使用上下文管理器来实现，从而有助于正确处理资源。\n",
    "\n",
    "答案：\n",
    "\n",
    "with 语句给需要处理的文件创建了一个上下文管理器对象，当 with 控制块运行完了，文件会自动关闭。\n",
    "\n",
    "问题中涉及的知识点：\n",
    "\n",
    "with 语句\n",
    "上下文管理器\n",
    "知识点解析：\n",
    "\n",
    "with 语句\n",
    "\n",
    "它的基本格式如下：\n",
    "\n",
    "with with_item:\n",
    "        do_something()\n",
    "with 语句的执行过程如下:\n",
    "\n",
    "对上下文表达式 (在 with_item 中给出的表达式) 求值以获得一个上下文管理器。\n",
    "载入上下文管理器的__exit__() 以便后续使用。\n",
    "发起调用上下文管理器的__enter__() 方法。\n",
    "如果 with 语句中包含一个目标，来自__enter__() 的返回值将被赋值给它。\n",
    "执行语句体。\n",
    "发起调用上下文管理器的__exit__() 方法。如果语句体的退出是由异常导致的，则其类型、值和回溯信息将被作为参数传递给__exit__()。否则的话，将提供三个 None 参数。\n",
    "with 语句的目的是为了简化 try/finally 模式。这种模式用于保证某段代码 A 被执行完毕后执行某项操作 B。而在执行那段代码 A 的过程中可能会有异常发生，或者其它的调用而终止，即使这样，最终还是会执行某项操作 B。\n",
    "\n",
    "比如使用 with 语句去读文件，就可以确保无论中间发生什么，这个文件都会被关闭。\n",
    "\n",
    "Tips: 简而言之，当出现 with 语句的时候，对象的__enter__() 方法被调用，它返回的值会被赋给 as 后面的变量，然后 with 语句块里的语句被执行，最后，无论在 with 语句块里的语句发生了什么情况，__exit__() 方法最后都会被调用做最后的清理工作。\n",
    "\n",
    "上下文管理器对象\n",
    "\n",
    "它定义了在执行 with 语句时要建立的运行时上下文。上下文管理器处理进入和退出所需运行时上下文以执行代码块。Python 的上下文管理器协议包含了两个方法__enter__() 方法和__exit__()。\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 延伸问题：在 python 中读取大文件"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "这道题中有两个关键点就是大文件以及最后去释放这个文件资源。所以，1）我们就得想办法让它不一次性的把文件的内容往内存中放；2）无论怎样，最后都要关闭文件。\n",
    "\n",
    "答案：\n",
    "\n",
    "方法 1：\n",
    "\n",
    "with open('filename') as file:\n",
    "  for line in file:\n",
    "    do_something(line)\n",
    "方法 2：\n",
    "\n",
    "def readInChunks(fileObj, chunkSize=2048):\n",
    "  while True:\n",
    "    data = fileObj.read(chunkSize)\n",
    "\n",
    "    if not data:\n",
    "      break\n",
    "\n",
    "    yield data\n",
    "\n",
    ">>> f = open('bigFile')\n",
    ">>> for chuck in readInChunks(f):\n",
    "...    do_something(chunk)\n",
    ">>> f.close()\n",
    "问题中涉及的知识点：\n",
    "\n",
    "with open 的用法\n",
    "文件对象是可迭代的\n",
    "生成器处理大数据\n",
    "知识点解析：\n",
    "\n",
    "方法 1 直接利用 with 语句的特性，上下文管理对象去处理打开和关闭文件，包括如果内部块中引发异常。for 行将文件对象 file 视为可迭代，它会自动使用缓冲的 I/O 和内存管理，因此您不必担心大型文件。\n",
    "\n",
    "方法 2 则是自己手动去写一个生成器函数去返回一个迭代器，遍历一次就输出一个文件数据块，这样也不会一次把内存都占满。但是麻烦的是，你需要手动关闭文件。\n",
    "\n",
    "Tips: 我们可以将方法 2 理解为方法 1 的手动版，在方法 1 的幕后 Python 解释器就像按方法 2 一样的步骤去执行。\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 小结"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "在这几个小节中，我们进一步知道了：\n",
    "\n",
    "pass 语句\n",
    "字符串的创建\n",
    "每个数据对象都有编号，值，类型\n",
    "实例的创建\n",
    "with 语句和上下文管理器"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.8.3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 1
}